package com.hhsjtest.composiable.scroll;

import static com.hhsjtest.composiable.scroll.ScrollStateUtil.SCROLL_DOWN;
import static com.hhsjtest.composiable.scroll.ScrollStateUtil.SCROLL_UP;
import static com.hhsjtest.composiable.scroll.ScrollStateUtil.scrollMode;
import static com.hhsjtest.composiable.scroll.ScrollStateUtil.scrollModeName;

import android.content.Context;
import android.graphics.Color;
import android.hardware.SensorManager;
import android.os.StrictMode;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.ViewGroup;

import androidx.core.widget.NestedScrollView;

import java.io.File;
import java.lang.reflect.Field;


public class RootScrollView extends NestedScrollView {

    InnerListView listView;
    float lastY = 0;

    //ListView从不完整到完全展示的时刻的标记
    boolean startDownShowHoleListView = false;
    boolean startUpHideListView = false;

    int listViewHideHeight = 0;

    /**
     * 正在触摸滑动
     */
    boolean isTouchScrolling = false;

    public RootScrollView(Context context) {
        super(context);
        init();
    }

    public RootScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public RootScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    void init() {
        setBackgroundColor(Color.RED);
    }

    public void setInnerListView(InnerListView listView) {
        this.listView = listView;
    }

    /**
     * 一但拦截触发了，mFirstTouchTarget就会被清空，且事件会中断，并且之后的事件不在向下分发，而是给自身处理
     *
     * @param ev
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (listView == null) {
            return super.onInterceptTouchEvent(ev);
        }

        final int action = ev.getActionMasked();
        if (action == MotionEvent.ACTION_DOWN) {
            lastY = ev.getY();
        }
        //父类到move事件一般情况是拦截，但是我们需要处理到是在ListView到达顶部之后，如果继续向上就不拦截，
        if (action == MotionEvent.ACTION_MOVE) {
            Log.e("HHSJ", "Root intercept 进入 move 判断");
            final int currentY = (int) ev.getY();
            if (getScrollY() == listView.getY() && currentY > lastY) {
                lastY = currentY;
                return false;
            }
        }

        if (action == MotionEvent.ACTION_UP) {
            Log.e("HHSJ", "----Up事件，进入拦截");
            //如果ScrollView 在滚动的时候 up，up是需要拦截给自己处理的
            Class<ViewGroup> viewGroupClass = ViewGroup.class;
            try {
                Field field = viewGroupClass.getDeclaredField("mFirstTouchTarget");
                field.setAccessible(true);
                Object o = field.get(this);
                if (o == null) {
                    Log.e("HHSJ", " mFirstTouchTarget = null");
                } else {
                    Log.e("HHSJ", " mFirstTouchTarget = " + o.hashCode());

                    Field field1 = o.getClass().getField("child");
                    field1.setAccessible(true);
                    Object child = field1.get(o);
                    if (child != null) {
                        Log.e("HHSJ", "TouchTarget child " + child.hashCode());
                    } else {
                        Log.e("HHSJ", "TouchTarget child = null");
                    }
                }

                Field field2 = this.getClass().getSuperclass().getDeclaredField("mIsBeingDragged");
                if (field2 != null) {
                    field2.setAccessible(true);
                    boolean mIsBeingDragged = (boolean) field2.get(this);
                    if (mIsBeingDragged) {
                        Log.e("HHSJ", "mIsBeingDragged 正在滑动");
                        if (getScrollY() < listView.getY()) {
                            Log.e("HHSJ", "mIsBeingDragged 正在滑动 且listview没有到顶部 拦截up");
                            return true;
                        }
                    } else {
                        Log.e("HHSJ", "mIsBeingDragged 没有滑动");
                    }
                }
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (listView == null) {
            return super.onTouchEvent(ev);
        }
        final float currentY = ev.getY();
        int action = ev.getActionMasked();
        if (action == MotionEvent.ACTION_DOWN) {
            lastY = currentY;
            isTouchScrolling = false;
        }
        if (action == MotionEvent.ACTION_MOVE) {
            Log.e("HHSJ", "Root touch move ===== " + String.format("ScrollY = %d", getScrollY()));
        }

        if (action == MotionEvent.ACTION_UP) {
            Log.e("HHSJ", "》》》《Root touch Up");
            upY = (int) currentY;
            if (isTouchScrolling) {
                Log.e("HHSJ", "》》》《Root touch Up Scrolling");
                return super.onTouchEvent(ev);
            }
            Log.e("HHSJ", "》《《《Root touch Up 2");
        }
        return super.onTouchEvent(ev);
    }


    /**
     * 1：只有调用 super.dispatchTouchEvent 才能给 mFirstTouchTarget 赋值。
     * 2：super.dispatchTouchEvent 可能使得子视图调用了onTouchEvent，产生了事件效果。
     * 3：如果子视图产生了事件效果后，还需要父视图处理，则需要继续调用一次onTouchEvent，
     * 不过需要对Event的位置做一次重定位。
     * 4：如果父视图想每次都让子视图处理，处理后又给到自己，那么子视图的dispatchTouchEvent一定要返回true，
     * 因为返回false后，父视图的mFirstTouchTarget就为null，此时调用super.dispatchTouchEvent就不会分发，
     * 而是自己处理，走父视图的onTouchEvent。
     *
     * @param ev
     * @return
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (listView == null) {
            return super.dispatchTouchEvent(ev);
        }

        int action = ev.getActionMasked();
        if (action == MotionEvent.ACTION_DOWN) {
            //down的时候，需要求出ListView已经滚动的高度，用于scrollView处理回滚
            lastY = ev.getY();
            listViewHideHeight = listView.getHideY();
            Log.e("HHSJ", "listViewHideHeight :" + listViewHideHeight);
        }

        //1，必须发事件分发下去
        if (ev.getAction() == MotionEvent.ACTION_MOVE) {
            //确保事件都能传递下去
            requestDisallowInterceptTouchEvent(true);
            Log.e("HHSJ", "     ----------------\nRoot dispatch move ===== " + String.format("ScrollY = %d", getScrollY()));
            final float currentY = ev.getY();
            final int scrollMode = scrollMode((int) currentY, (int) lastY);
            Log.e("HHSJ", "Root dispatch move " + scrollModeName(scrollMode));
            lastY = currentY;
            //是否到顶
            final boolean isListAtTop = listView.getY() == getScrollY();
            //超出顶部
            if (listView.getY() < getScrollY()) {//这是一个异常情况
                Log.e("HHSJ", "Root dispatch move ：超出顶部，");
                return true;
            } else if (listView.getY() > getScrollY()) {
                Log.e("HHSJ", "Root dispatch move ：未到顶部");
            }

            /**
             *
             * 此时800，
             *  向下，如果ListView 不能向下，则交给ScrollView onTouch ，结束后就小于800
             *          ListView 可以向下，交给ListView onTouch，继续判断是否可以向下，直至不能向下
             * 799
             *  向下，分发给ListView listView 不能向下, 不走onTouch，返回true, 交给ScrollView
             *
             * 当到达顶部，
             * 当向下滚动的时候，scrollView尝试交给ListView处理，
             *  但是由于listView在向下滚动的时候，发现自己不能滚动，就直接返回了true表示我处理了，但是ListView的onTouch方法不用掉，所以需要调用ScrollView的onTouch
             *  于是就是ScrollView::super.dispatch -> ListView dispatch return true , ScrollView 继续调用 onTouchEvent
             * 当向上滚动的时候，scrollView还是尝试交给ListView处理
             *  由于ListView此时可以向上滚动，就自己处理，走了super.dispatch，并走了onTouch
             */

            Log.e("HHSJ", "Root dispatch MOVE ");

            // 当 上滑的时候，就算分发给子视图，子视图也是无法滑动的，此时子视图返回了true，所以自身还得继续执行滚动，就得走onTouch
            // 当 下滑的时候， 如果子视图不能向下滑动，最终肯定是自己处理的
            if (isListAtTop) {//到顶
                Log.e("HHSJ", "Root dispatch MOVE  此刻List在Scroll顶部");
                //向上，且listView可以向上，将只做分发，因为listView会自己处理滚动，并返回，所以scroll不能滚动
                //上移的时候，告诉listView可以拦截，move标记拦截，同时告诉自己，别拦截事件
                //向下，且listView可以向下，将只做分发，如果listView不能向下，那么就自己接管
                if (scrollMode == SCROLL_DOWN) {
                    Log.e("HHSJ", "Root dispatch MOVE  此刻List在Scroll顶部; 下滑;");
                    isTouchScrolling = true;
                    //可以向下滚动
                    if (listView.canScrollVertically(-1)) {
                        //只要还在顶部，而且ListView可以向下滑，就说明ListView还没有做好完整展示的准备
                        startDownShowHoleListView = false;
                        Log.e("HHSJ", "Root dispatch MOVE  此刻List在Scroll顶部 下滑; List 可下滑 ,ListView 可自己处理;");
                        super.dispatchTouchEvent(ev);
                        //如果ListView没有处理，表明还需要ScrollView处理
                        if (!listView.handleMove()) {
                            onTouchEvent(ev);
                        }
                    } else {
                        Log.e("HHSJ", "Root dispatch MOVE  此刻List在Scroll顶部 下滑; List 不可下滑");
                        //listView不能向下，就自己处理

                        final int beforeY = (int) ev.getY();
                        super.dispatchTouchEvent(ev);
                        final int afterY = (int) ev.getY();
                        Log.e("HHSJ", String.format("before: %d , after: %d", beforeY, afterY));
                        Log.e("HHSJ", "Root dispatch MOVE  此刻List在Scroll顶部 下滑; List 不可下滑; ScrollView 自行处理;");
                        /**
                         * 这是一个非常重要的一步
                         * 这个滚动之后，ListView将非常可能不在ScrollView顶部
                         */
                        onTouchEvent(ev);
                        Log.e("HHSJ", "Root dispatch MOVE  此刻List在Scroll顶部 下滑; List 不可下滑; ScrollView 自行处理; ScrollView 处理完毕;");

                        /**
                         * 核心代码 2
                         */
                        if (!startDownShowHoleListView) {
                            Log.e("HHSJ", "RootDis super dispatch 2  " + getScrollY());
                            //确保ScrollView在ListView刚到达顶部的时候，继续下滑时ScrollView不跳动
                            scrollTo(0, (int) listView.getY() - 1);
                        }
                        startDownShowHoleListView = true;
                    }
                    return true;
                }

                if (scrollMode == SCROLL_UP) {
                    isTouchScrolling = true;
                    Log.e("HHSJ", "Root dispatch MOVE  此刻List在Scroll顶部; 上滑");
                    if (listView.canScrollVertically(1)) {
                        Log.e("HHSJ", "Root dispatch MOVE  此刻List在Scroll顶部; 上滑; List 可上滑");
                        boolean dispatch = super.dispatchTouchEvent(ev);
                        onTouchEvent(ev);
//                        if (!startUpHideListView) {
//                            scrollTo(0, (int) listView.getY());
//                        }
                        startUpHideListView = true;
                        return true;
                    } else {
                        startUpHideListView = false;
                        Log.e("HHSJ", "Root dispatch MOVE  此刻List在Scroll顶部; 上滑; List也在顶部，此刻还不可上滑");
                        lastY = currentY;
                        super.dispatchTouchEvent(ev);
                        Log.e("HHSJ", "Root dispatch MOVE  此刻List在Scroll顶部; 上滑; List也在顶部，此刻还不可上滑; Scroll 需要自己touch");
                        return onTouchEvent(ev);
                    }
                }
                return true;
            } else {//ListView没有滚到ScrollView顶部
                if (scrollMode == SCROLL_DOWN) {
                    super.dispatchTouchEvent(ev);
                    /**
                     * 这个下滑的时候，一般是在从顶部到非顶部的连贯过程，或者一开始就是这个状态，此时是由ScrollView处理
                     * 如果listView可以向下滚动，其实是一个异常情况
                     */
                    //listView可向下滚动
                    if (listView.canScrollVertically(-1)) {
                        Log.e("HHSJ", "正常情况下，当ListView不在ScrollView顶部的时候是不能向下滑动的，但是这里是可向下滑动，所以这是一个异常情况");
                        /**
                         * 虽然是异常情况，也还是需要把ListView滚下来
                         */
                    } else {
                        Log.e("HHSJ", "RootDis super dispatch 1");
                        /**
                         * 先还是分发给ListView，但是ListView不走onTouch的滚动，只是返回了true，
                         * 所以super.dispatch无法触发ScrollView的onTouch，此时需要手动调用onTouch触发滚动
                         */
                        if (!listView.handleMove()) {
                            onTouchEvent(ev);
                        }
                        if (!startDownShowHoleListView) {
//                            Log.e("HHSJ", "RootDis super dispatch 2");
                            //开始展示完整的ListView，此时需要把down的时候藏在里面的ListView的高度给剪掉，
//                            scrollTo(0, (int) listView.getY());
                        }
                        startDownShowHoleListView = true;
                    }
                }

                /**
                 * ListView不在ScrollView的顶端的时候，ListView是不应该滑动的，需要在ListView的dispatchTouch中直接返回true，同理
                 */
                if (scrollMode == SCROLL_UP) {
                    //list向上划的时候，scrollview
                    boolean dispatch = super.dispatchTouchEvent(ev);
                    if (!listView.handleMove()) {
                        onTouchEvent(ev);
                    }
                    //作用分发，由于listView处理了一次返回true,
                    Log.e("HHSJ", "Root dispatch move " + scrollModeName(scrollMode) + "====== result : " + dispatch);
                }
            }
            return true;
        }

        if (action == MotionEvent.ACTION_UP) {
            requestDisallowInterceptTouchEvent(false);
//            return super.dispatchTouchEvent(ev);
            /**
             * 思考下方案：如果ScrollView正在滚动，当Up的时候，不能让ListView操作Up，即ListView的 onTouch Up如果返回false？
             */
            final int beforeY = (int) ev.getY();
            boolean dispatch = super.dispatchTouchEvent(ev);
            final int afterY = (int) ev.getY();
            Log.e("HHSJ", String.format("Up before-after: %d: %d", beforeY, afterY));
            //如果子视图 返回了false
            if (!dispatch) {
                Log.e("HHSJ", "子视图没有消费Up事件");
            } else {
                Log.e("HHSJ", "子视图消费Up事件");
            }
            //这个onTouch(ev)用于保证ScrollView的抬手动作
            onTouchEvent(ev);

            if (isTouchScrolling) {
                isTouchScrolling = false;
            }
            return true;
        }
        return super.dispatchTouchEvent(ev);
    }



    public double getSplineFlingDistance(int velocityY) {

        final double l = getG(velocityY);
        final double decelMinusOne = (float) (Math.log(0.78) / Math.log(0.9)) - 1.0;
        return ViewConfiguration.getScrollFriction() * (SensorManager.GRAVITY_EARTH // g (m/s^2)
                * 39.37f // inch/meter
                * (getContext().getResources().getDisplayMetrics().density * 160.0f)
                * 0.84f) * Math.exp((float) (Math.log(0.78) / Math.log(0.9)) / decelMinusOne * l);
    }


    public double getG(int velocityY) {
        return Math.log(0.35f * Math.abs(velocityY) / (ViewConfiguration.getScrollFriction() * SensorManager.GRAVITY_EARTH
                * 39.37f // inch/meter
                * (getContext().getResources().getDisplayMetrics().density * 160.0f)
                * 0.84f));

    }

    int upY = 0;

    /**
     * 通过目标y得到需要的初速度 （指头向上滑，需要的初速度）
     *
     * @param endY
     * @return
     */
    public int getVelocityY(int endY) {
        int signum = -1;//如果指头上滑，他肯定是-1；
        double dis = (endY - upY) * signum;
        double g = Math.log(dis / ViewConfiguration.getScrollFriction() / (SensorManager.GRAVITY_EARTH // g (m/s^2)
                * 39.37f // inch/meter
                * (getContext().getResources().getDisplayMetrics().density * 160.0f)
                * 0.84f)) * Math.log(0.9) * ((float) (Math.log(0.78) / Math.log(0.9)) - 1.0) / (Math.log(0.78));
        return (int) (Math.exp(g) / 0.35f * (ViewConfiguration.getScrollFriction() * SensorManager.GRAVITY_EARTH
                * 39.37f // inch/meter
                * (getContext().getResources().getDisplayMetrics().density * 160.0f)
                * 0.84f));
    }

    /**
     * @param velocityY 初速度
     * @return 滑动的距离
     * @author xuekai
     */
    public double getDis(int velocityY) {

        final double l = getG(velocityY);
        final double decelMinusOne = (float) (Math.log(0.78) / Math.log(0.9)) - 1.0;
        return ViewConfiguration.getScrollFriction() * (SensorManager.GRAVITY_EARTH // g (m/s^2)
                * 39.37f // inch/meter
                * (getContext().getResources().getDisplayMetrics().density * 160.0f)
                * 0.84f) * Math.exp((float) (Math.log(0.78) / Math.log(0.9)) / decelMinusOne * l);
    }
}
